/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		mesh.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-02-20 17:45
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-06-07 21:39
*
*   Description:
*
=============================================================================*/
#ifndef MESH_H
#define MESH_H

#include "../../util/iterator.hpp"
#include "../shape/shape_interface.hpp"
#include "mask.hpp"
#include <algorithm>
#include <list>

namespace geoxfem {
namespace geometry {

template <typename DimensionType, typename degree>
struct Mesh {
  using point_type = typename traits::point_type<DimensionType>::type;
  using Shape      = ShapeInterface<DimensionType, degree>;
  using Mask       = MeshMask<Shape>;
  using ptree      = boost::property_tree::ptree;
  using PointList  = std::list<point_type>;
  using ShapeList  = std::vector<Shape*>;
  using SELF       = Mesh<DimensionType, degree>;
  static constexpr size_t dimension = traits::dimension<DimensionType>::value;

  /*constructor{{{*/

  Mesh() : m_list_point{}, m_list_shape{4} {}
  Mesh(const Mesh&) = default;
  Mesh(Mesh&&)      = default;
  virtual ~Mesh()   = default;
  Mesh& operator=(const Mesh&) = default;
  Mesh& operator=(Mesh&&) = default;
  /*}}}*/
  /*point manuvar{{{*/

  /**
   * @brief append
   *
   * @param point
   *
   * @return
   */
  bool append(point_type point) {
    auto it = std::find(m_list_point.begin(), m_list_point.end(), point);
    if(it == m_list_point.end()) {
      m_list_point.emplace_back(point);
      return true;
    }
    return false;
  }
  PointList& getPoints() {
    return m_list_point;
  }

  /*}}}*/
  /*shapes manuvar{{{*/

  /**
   * @brief addShape
   *
   * @param shp
   */
  virtual void append(Shape* shp) {
    auto& list = m_list_shape.at(shp->getTopologyDimension());
    auto  it   = std::find(list.begin(), list.end(), shp);
    if(it == list.end())
      list.emplace_back(shp);
    for(auto& point : pointsIn(*shp))
      append(point);

    return;
  }
  /**
   * @brief getShape
   *
   * @param dim
   * @param n
   *
   * @return
   */
  Shape* getShape(const size_t& dim, const size_t& n) const {
    return m_list_shape.at(dim).at(n);
  }
  /**
   * @brief getVertex
   *
   * @param n
   *
   * @return
   */
  Shape* getVertex(const size_t& n) const {
    return getShape(0, n);
  }
  /**
   * @brief getCell
   *
   * @param n
   *
   * @return
   */
  Shape* getCell(const size_t& n) const {
    return getShape(getTopologyDimension(), n);
  }
  /**
   * @brief getFacet
   *
   * @param n
   *
   * @return
   */
  Shape* getFacet(const size_t& n) const {
    return getShape(getTopologyDimension() - 1, n);
  }
  /**
   * @brief getNumberofShapes
   *
   * @param dim
   *
   * @return
   */
  size_t getNumberOfShapes(const size_t& dim) const {
    return m_list_shape.at(dim).size();
  }
  /**
   * @brief getNumberOfNodes
   *
   * @return
   */
  size_t getNumberOfVertices() const {
    return getNumberOfShapes(0);
  }
  /**
   * @brief getNumberOfFacets
   *
   * @return
   */
  size_t getNumberOfFacets() const {
    return getNumberOfShapes(getTopologyDimension() - 1);
  }
  /**
   * @brief getNumberOfElement
   *
   * @return
   */
  size_t getNumberOfCells() const {
    return getNumberOfShapes(getTopologyDimension());
  }
  /*}}}*/
  /*iterator access{{{*/

  typename PointList::iterator getPointBegin() {
    return m_list_point.begin();
  }
  typename PointList::iterator getPointEnd() {
    return m_list_point.end();
  }
  typename PointList::const_iterator getPointBegin() const {
    return m_list_point.begin();
  }
  typename PointList::const_iterator getPointEnd() const {
    return m_list_point.end();
  }

  typename ShapeList::iterator getShapeBegin(const size_t& dim) {
    return m_list_shape.at(dim).begin();
  }
  typename ShapeList::iterator getShapeEnd(const size_t& dim) {
    return m_list_shape.at(dim).end();
  }
  typename ShapeList::const_iterator getShapeBegin(const size_t& dim) const {
    return m_list_shape.at(dim).begin();
  }
  typename ShapeList::const_iterator getShapeEnd(const size_t& dim) const {
    return m_list_shape.at(dim).end();
  }
  /*}}}*/
  /*misc{{{*/

  /**
   * @brief getTopologyDimension
   *
   * @return
   */
  size_t getTopologyDimension() const {
    for(size_t topo_dim = 1; topo_dim != 4; ++topo_dim)
      if(getNumberOfShapes(topo_dim) == 0)
        return topo_dim - 1;
    return 3;
  }
  /**
   * @brief getClassName
   *
   * @return
   */
  std::string getClassName() const {
    return "Mesh";
  }
  void plot(const std::string& seg_cfg = " w l ls 2 lc rgb \"blue\"",
            const std::string& gp_cfg  = " w p pt 0 lc rgb \"red\"",
            const std::string&         = "\0") const {
    io::DataPloter plt;
    for(auto cell : shapesIn(*this, getTopologyDimension()))
      cell->plot(plt, seg_cfg, gp_cfg);
  }
  void plot(io::DataPloter&    plt,
            const std::string& seg_cfg = " w l ls 2 lc rgb \"blue\"",
            const std::string& gp_cfg  = " w p pt 0 lc rgb \"red\"",
            const std::string&         = "\0") const {
    for(auto cell : shapesIn(*this, getTopologyDimension()))
      cell->plot(plt, seg_cfg, gp_cfg);
  }

  /**
   * @brief operator<< >
   *
   * @param os
   * @param mesh
   *
   * @return
   */
  friend std::ostream& operator<<(std::ostream& os, const SELF& mesh) {
    os << "\nMesh :\n";
    for(auto& cell : shapesIn(mesh, mesh.getTopologyDimension()))
      os << '\t' << *cell << '\n';
    return os;
  }
  /*}}}*/
  /*extract from parent mesh{{{*/

  void extractShapeFrom(const SELF& mesh, const Mask& mask) {
    size_t     topo_dim = mesh.getTopologyDimension();
    ShapeList& list     = mesh.m_list_shape.at(topo_dim);
    std::copy_if(list.begin(), list.end(),
                 std::back_inserter(m_list_shape.at(topo_dim)), mask);
    return;
  }
  void extractPointFrom(const SELF& mesh, const Mask& mask) {
    size_t     topo_dim = mesh.getTopologyDimension();
    ShapeList& list     = mesh.m_list_point.at(topo_dim);
    std::copy_if(list.begin(), list.end(),
                 std::back_inserter(m_list_point.at(topo_dim)), mask);
    return;
  }
  /*}}}*/

protected:
  /* point and node sometimes need sorting or indexing. So std::set is used.
   * shape and element use unordered_set instead. */

  /* point list for access points */
  PointList m_list_point;
  /* point list for access shapes */
  std::vector<ShapeList> m_list_shape;
};

}  // namespace geometry

namespace traits {

template <typename DimensionType, typename degree>
struct dimension<geometry::Mesh<DimensionType, degree>> { /*{{{*/
  static constexpr size_t value = dimension<DimensionType>::value;
}; /*}}}*/

template <typename DimensionType, typename degree>
struct get_topology_dimension</*{{{*/
                              geometry::Mesh<DimensionType, degree>> {
  using Mesh = geometry::Mesh<DimensionType, degree>;
  static size_t apply(const Mesh& mesh) {
    return mesh.getTopologyDimension();
  }
}; /*}}}*/

}  // namespace traits

namespace algorithm {

template <typename DimensionType, typename degree, typename GaussFunc>
auto integral(const geometry::Mesh<DimensionType, degree>& mesh, /*{{{*/
              const GaussFunc&                             func)
  -> decltype(func(geometry::ShapeInterface<DimensionType, degree>{},
                   model::GaussPoint<DimensionType>{})) {
  using Shape = geometry::ShapeInterface<DimensionType, degree>;
  using Gauss = model::GaussPoint<DimensionType>;

  size_t         mesh_topo_dim = mesh.getTopologyDimension();
  Shape*         shp           = *mesh.getShapeBegin(mesh_topo_dim);
  Gauss          gp            = *shp->getGaussPointBegin();
  decltype(auto) ans           = func(*shp, gp) * 0;
  for(auto& cell : shapesIn(mesh, mesh_topo_dim))
    ans += integral(*cell, func);
  return ans;
} /*}}}*/

}  // namespace algorithm
}  // namespace geoxfem

#endif /* MESH_H */
